/*
 * native Windows file I/O 
 * Copyright (c) 2012 Juergen Bohl
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <Windows.h>
#include "libavutil/avstring.h"
#include "avformat.h"
#include "url.h"

static HANDLE CreateFileUTF8( LPCSTR lpFileName,
                              DWORD dwDesiredAccess,
                              DWORD dwShareMode,
                              LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                              DWORD dwCreationDisposition,
                              DWORD dwFlagsAndAttributes,
                              HANDLE hTemplateFile )
{
    int len = MultiByteToWideChar(CP_UTF8,0,lpFileName,-1,NULL,0);
    if (len==0)
        return INVALID_HANDLE_VALUE;
    //  function returns the required buffer size, in characters, including any terminating null character
    WCHAR szwFileName[len];
    int r = MultiByteToWideChar(CP_UTF8,0,lpFileName,-1,szwFileName,len);
    if (r==0)
        return INVALID_HANDLE_VALUE;
    return CreateFileW(szwFileName,dwDesiredAccess,dwShareMode,lpSecurityAttributes,dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile);
}

static DWORD GetFileAttributesUTF8(LPCSTR lpFileName)
{
    int len = MultiByteToWideChar(CP_UTF8,0,lpFileName,-1,NULL,0);
    if (len==0)
        return INVALID_FILE_ATTRIBUTES;
    //  function returns the required buffer size, in characters, including any terminating null character
    WCHAR szwFileName[len];
    int r = MultiByteToWideChar(CP_UTF8,0,lpFileName,-1,szwFileName,len);
    if (r==0)
        return INVALID_FILE_ATTRIBUTES;
    return GetFileAttributesW(szwFileName);
}

static int file_open(URLContext *h, const char *filename, int flags)
{
    av_strstart(filename, "winfile:", &filename);
    // we expect to get an UTF8 encoded string
    HANDLE hFile = INVALID_HANDLE_VALUE;
    if (flags&AVIO_FLAG_READ && !(flags&AVIO_FLAG_WRITE))
    {
        hFile = CreateFileUTF8( filename,
                         GENERIC_READ,
                         FILE_SHARE_READ,
                         NULL,
                         OPEN_EXISTING,
                         FILE_FLAG_SEQUENTIAL_SCAN,
                         NULL);
    }
    else if (!(flags&AVIO_FLAG_READ) && flags&AVIO_FLAG_WRITE)
    {
        // we want to overwrite the file (CREATE_ALWAYS), avio seems to check for file-existence
        // before and if we get here, we are sure that the user has agreed to overwrite the file
        hFile = CreateFileUTF8( filename,
                                GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                CREATE_ALWAYS,
                                0,
                                NULL);
    }
    else if ( flags&(AVIO_FLAG_WRITE|AVIO_FLAG_READ) == (AVIO_FLAG_WRITE|AVIO_FLAG_READ))
    {
        hFile = CreateFileUTF8( filename,
                                GENERIC_WRITE|GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                CREATE_ALWAYS,
                                0,
                                NULL);
    }

    if (hFile != INVALID_HANDLE_VALUE)
    {
        h->priv_data = (void*)hFile;
        return 0;
    }

    DWORD lastError = GetLastError();
    switch (lastError)
    {
    case ERROR_FILE_NOT_FOUND:
        return AVERROR(ENOENT);
    default:
        return AVERROR(EIO);
    }
}

static int file_read(URLContext *h, unsigned char *buf, int size)
{
    HANDLE hFile =  (HANDLE)h->priv_data;
    DWORD bytesRead;
    BOOL B = ReadFile(hFile,buf,size,&bytesRead,NULL);
    if (B==0)
    {
        return AVERROR(EBADF);
    }

    return bytesRead;
}


static int file_write(URLContext *h, const unsigned char *buf, int size)
{
    HANDLE hFile =  (HANDLE)h->priv_data;
    DWORD bytesWritten;
    BOOL B = WriteFile(hFile,buf,size,&bytesWritten,NULL);
    if (B==0)
    {
        return AVERROR(EBADF);
    }

    return bytesWritten;
}


static int64_t file_seek(URLContext *h, int64_t pos, int whence)
{
    HANDLE hFile =  (HANDLE)h->priv_data;
    if (whence&AVSEEK_SIZE)
    {
        // trouble is: in av_get_packet we call into ffio_limit, which tries to determine the
        // filesize, which is in case of a pipe, well, troublesome. In our case it causes the
        // truncation of the packet (because we report in this case the current stream pointer).
        // some ideas: - specify the filesize (if known) somehow so that it can be reported here (in the filename?)
        //             - if unknown, well, return that it is unknown, or return a very high number
        if (GetFileType(hFile)==FILE_TYPE_PIPE)
        {
            return INT64_MAX;
        }
        else
        {
            LARGE_INTEGER li;
            BOOL B = GetFileSizeEx(hFile,&li);
            if (B==0)
            {
                return AVERROR(EBADF);
            }

            return li.QuadPart;
        }
    }
    else
    {
        LARGE_INTEGER actualPos;
        LARGE_INTEGER li;
        li.QuadPart = pos;
        BOOL B = SetFilePointerEx(hFile,li,&actualPos,FILE_BEGIN);
        if (B==0)
        {
            return AVERROR(EBADF);
        }

        return actualPos.QuadPart;
    }
}

static int file_close(URLContext *h)
{
    HANDLE hFile =  (HANDLE)h->priv_data;
    BOOL B = CloseHandle(hFile);
    if (B!=0)
        return 0;
    return AVERROR(EIO);
}

static int file_check(URLContext *h, int mask)
{
    LPCSTR szFileName;
    av_strstart(h->filename, "winfile:", &szFileName);
    DWORD attribs = GetFileAttributesUTF8(szFileName);
    if (attribs==INVALID_FILE_ATTRIBUTES)
    {
        switch (GetLastError())
        {
        case ERROR_FILE_NOT_FOUND:
            return AVERROR(ENOENT);
        default:
            return AVERROR(EIO);
        }
    }

    int ret = mask & (AVIO_FLAG_WRITE|AVIO_FLAG_READ);
    if (mask&AVIO_FLAG_WRITE && attribs&FILE_ATTRIBUTE_READONLY)
        ret &= ~AVIO_FLAG_WRITE;

    return ret;
}



URLProtocol ff_winfile_protocol = {
    .name                = "winfile",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_check           = file_check,
};
